package com.mydemo.charging.device.repository;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.mydemo.charging.common.pojo.po.ChargingStationPO;
import com.mydemo.charging.common.pojo.query.NearStationsQuery;
import com.mydemo.charging.common.pojo.vo.StationInfoVO;
import com.mydemo.charging.device.mapper.StationMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.geo.*;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.GeoOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.domain.geo.GeoLocation;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Repository
public class StationRepository {
    @Autowired
    private StationMapper stationMapper;
    @Resource
    @Qualifier("redis01")
    private RedisTemplate redisCache;

    public Long getTotal() {
        // select count(0)
        // from charging_station
        return stationMapper.selectCount(null);
    }

    public List<ChargingStationPO> getPage(int pageNo, int pageSize) {
        int from=(pageNo-1)*pageSize;
        //pageNo=1 pageSize=1000 查询第一批1000条
        // select *
        // from station
        // limit 0,1000;
        //pageNo=2 pageSize=1000 查询第二批1000条
        // select *
        // from station
        // limit 1000,1000
        Page<ChargingStationPO> page=new Page<>(pageNo,pageSize);
        Page<ChargingStationPO> pagePos = stationMapper.selectPage(page, null);
        return pagePos.getRecords();
    }

    public Object nearStations(NearStationsQuery query) {
        // 提前把操作缓存数据的客户端对象 获取
        GeoOperations geoOps = redisCache.opsForGeo();
        ValueOperations<String,ChargingStationPO> stringOps = redisCache.opsForValue();
        // 利用中心点 和距离 构造 georadius命令 命中一批 stationId(绑定地理位置 距离中心位移)
        // radius beijing:stations query.lon query.lat 10000 m withcoord withdist
        RedisGeoCommands.GeoRadiusCommandArgs args
                = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().
                includeCoordinates().includeDistance();
        Circle circle=new Circle(
                new Point(query.getLongitude(),query.getLatitude()),
                new Distance(10000.0));
        GeoResults<GeoLocation<Integer>> result = geoOps.radius("beijing:stations", circle, args);
        // 取 绑定坐标 距离 每个元素值 都在list元素对象里GeoResult
        List<GeoResult<GeoLocation<Integer>>> targets = result.getContent();
        // 对geo查询的集合做 映射 利用stationId 读取string类型缓存使用stationName
        // 如果targets非空 附近查询是命中数据 将命中的每个包含地理位置 位移 站场id的list对象转化成vo
        if (targets!=null&&targets.size()>0){
            return targets.stream().map(target->{
                //转化封装 StationInfoVO
                StationInfoVO vo=new StationInfoVO();
                //distance 附加属性 距离位移
                BigDecimal distance = new BigDecimal(target.getDistance().getValue());//米单位换算成千米
                log.debug("查询距离:{}米",distance);
                //除以1000-->新的bigDecimal 3928.6742->3.9286742 保证取2位小数点 四舍五入3.93
                BigDecimal dist=distance.divide(new BigDecimal(1000)).setScale(2,BigDecimal.ROUND_HALF_UP);
                log.debug("查询距离:{}千米",dist);
                vo.setDistance(dist.doubleValue());
                //lng lat geo绑定地理位置
                Point point = target.getContent().getPoint();
                vo.setStationLng(new BigDecimal(point.getX()));
                vo.setStationLat(new BigDecimal(point.getY()));
                //id geo元素值
                vo.setStationId(target.getContent().getName());
                //stationName geo没有 取string缓存 key值 结合stationId 读取po
                ChargingStationPO chargingStationPO = stringOps.get("station:detail:" + vo.getStationId());
                vo.setStationName(chargingStationPO.getStationName());
                return vo;
            }).collect(Collectors.toList());
        }else{
            return null;
        }
    }

    public ChargingStationPO getById(Integer stationId) {
        // 旁路缓存cache-aside读流程
        ValueOperations<String,ChargingStationPO> opsForValue = redisCache.opsForValue();
        // 预热的时候 将站场信息po写入到缓存,但是在仓储层查询缓存未必能命中
        ChargingStationPO po=null;
        po=opsForValue.get("station:detail:"+stationId);//如果防止穿透的解决方案是使用null值
        // 缓存是否命中应该换成exists判断key是否存在
        // 判断未命中
        if (po==null){
            // 数据未命中 缓存没有可能性非常低
            po=stationMapper.selectById(stationId);
            if (po!=null){
                // 缓存未命中,数据库命中了 保证缓存在下次可以命中
                opsForValue.set("station:detail:"+stationId,po);
            }else{
                // 缓存未命中 数据库也未命中 说明数据不存在 遇见穿透了 如果服务内部调用 错误数据 外部调用 恶意访问
                // 解决方案 存储null 设置最大过期时间 短时间null
                opsForValue.set("station:detail:"+stationId,null,20, TimeUnit.SECONDS);
                // 添加过滤器--布隆过滤器
            }
        }
        // po命中 读取数据库 null
        return po;
    }
}
